mod unix_random;

#[cfg(test)]
mod tests {
    use super::unix_random::uniform_variate;
    use std::convert::TryFrom;
    
    #[test]
    fn test_singlevalue() {
        let val: f32 = uniform_variate().expect("Error gathering the random number");
        assert!(1f32 > val && val > 0f32);        
    }

    #[test]
    #[ignore]
    fn test_distribution() {
        
        const EXPECTED_MEAN: f32 = 0.5f32;
        const VERIFICATION_TOLERANCE: f32 = 0.001f32;
        const VALUE_CNT: usize = 1_000_000;
        const PERCENTILES: [f32; 6] =
            [ 0.0, 0.1, 0.3, 0.5, 0.7, 0.9 ];
        
        let mut cnt: f32 = 0f32;
        let mut sample: Vec<f32> = Vec::with_capacity(VALUE_CNT);
        let mut sum: f32 = 0f32;
        for n in 0..VALUE_CNT {
            sample.push(uniform_variate().expect("Error gathering sample!"));
            sum = sum + sample[n];
            cnt = cnt + 1f32;
        }
        
        let current_mean = sum / cnt;
        println!("delta to expected mean = {:.2e}", (current_mean - EXPECTED_MEAN).abs());

        assert!(sample.len() == VALUE_CNT);
        assert!(current_mean < EXPECTED_MEAN + VERIFICATION_TOLERANCE);
        assert!(current_mean > EXPECTED_MEAN - VERIFICATION_TOLERANCE);

        sample.sort_by(|a, b| a.partial_cmp(b).unwrap());
        
        for perc in PERCENTILES.iter() {
            print!("asserting percentile {} ...", perc);
            let idx =
                unsafe { (cnt * perc).trunc().to_int_unchecked::<usize>() };

            let val: f32 = match sample.get(idx) {
                Some(k) => { *k }
                None => { panic!("Error verifying percentiles!"); }
            };

            println!("delta = {:.2e}", (val - perc).abs());
            
            assert!(val > perc - VERIFICATION_TOLERANCE);
            assert!(val < perc + VERIFICATION_TOLERANCE);
        }
        
        
    }

    use super::unix_random::choose_uniformly_from;
    
    #[test]
    fn test_choose_uniformly_from() {
        let domain: Vec<u8> = vec![ 1 ];
        let observed = choose_uniformly_from(&domain).expect("Error gathering random value!");
        assert!(domain[0] == observed);
    }

    #[test]
    #[ignore]
    fn test_choose_uniformly_from_distribution() {
        const VALUE_CNT: usize = 1_000_000;
        const TOLERANCE: usize = VALUE_CNT / 1000;
        let domain: Vec<u16> = vec![ 1, 2, 3, 4 ];
        let expected_counts: usize = VALUE_CNT / domain.len();
        let mut observed_counts: [usize; 4] = [ 0, 0, 0, 0];
        
        let mut sample = Vec::with_capacity(VALUE_CNT);
        
        for _ in 0..VALUE_CNT {
            sample.push(choose_uniformly_from(&domain).expect("Error gathering random value!"));
        }
            
        sample.sort();

        while sample.len() > 0 {
            let item = sample.pop().expect("No more elements!");
            let idx = usize::try_from(item).unwrap() - 1;
            observed_counts[idx] = observed_counts[ idx ] + 1;
        }

        for item in domain.iter() {
            print!("asserting occurrences of {}", item);
            let idx = usize::try_from(*item).unwrap() - 1;
            println!("occurrences of {} are {}", item, observed_counts[idx]);
            assert!(expected_counts + TOLERANCE > observed_counts[idx]);
            assert!(expected_counts - TOLERANCE < observed_counts[idx]);
        }
        
    }

    use super::unix_random::RandomItem;
    use super::unix_random::BiasedChooser;

    #[test]
    #[should_panic]
    fn test_probabilities_less_than_one_in_construction_of_biasedchooser() {
        let domain = vec![
            RandomItem { item:true, prob: 0.1f32 },
            RandomItem { item:false, prob: 0.1f32 },
        ];
        let _ = BiasedChooser::new(domain).unwrap();
    }

    #[test]
    #[should_panic]
    fn test_probabilities_greater_than_one_in_construction_of_biasedchooser() {
        let domain = vec![
            RandomItem { item:true, prob: 0.1f32 },
            RandomItem { item:false, prob: 1.1f32 },
        ];
        let _ = BiasedChooser::new(domain).unwrap();
    }

    
    #[test]
    fn test_biased_choosing() {
        const TRY_CNT: u16 = 10_000;
        let domain = vec![
            RandomItem { item: true, prob: 0f32 },
            RandomItem { item: false, prob: 1f32 },
        ];

        let chooser = BiasedChooser::new(domain).unwrap();

        for _ in 0..TRY_CNT {
            assert!(! chooser.choose().expect("Error gathering random choice"));
        }
    }


    use std::collections::HashMap;
    #[test]
    #[ignore]
    fn test_biased_choosing_distribution() {

        const VALUE_CNT: f32 = 1_000_000f32;
        const TOLERANCE: f32 = VALUE_CNT / 1000f32;
        let domain = vec![
            RandomItem { item: true, prob: 0.2f32 },
            RandomItem { item: false, prob: 0.8f32 },
        ];
        let mut expected_counts = HashMap::with_capacity(2);
        for ri in domain.iter() {
            expected_counts.insert(ri.item, ri.prob * VALUE_CNT);
        }
        let mut observed_counts = HashMap::with_capacity(2);
        
        let chooser = BiasedChooser::new(domain).unwrap();

        let mut curr_val: f32 = 0f32;
        while curr_val < VALUE_CNT {
            let it = chooser.choose().expect("Error gathering random value!");
            let cnt = observed_counts.entry(it).or_insert(0f32);
            *cnt += 1f32;
            curr_val += 1f32;
        }
        
        for (item, observed_cnt) in observed_counts.iter() {
            print!("asserting occurrences of {}", item);
            let expected_cnt = expected_counts.get(item).unwrap();
            println!("occurrences of {} are {}", item, observed_cnt);
            assert!(expected_cnt + TOLERANCE > *observed_cnt);
            assert!(expected_cnt - TOLERANCE < *observed_cnt);
        }
        
    }
}


